package com.riteny.common.httpclient;

import com.riteny.common.entity.CommonHttpParamMapNode;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;

import java.net.SocketTimeoutException;
import java.util.Base64;
import java.util.Map;

/**
 * @author Riteny
 * 2021/4/30  14:44
 */
public class CommonHttpRestfulTemplate {

    public static final Integer HTTP_METHOD_GET = 1;
    public static final Integer HTTP_METHOD_POST = 2;
    public static final Integer HTTP_METHOD_PUT = 3;
    public static final Integer HTTP_METHOD_DELETE = 4;

    private Integer connectionTimeout = 10000;
    private Integer readTimeout = 100000;
    private static RestTemplate restTemplate;
    private String url;
    private Integer httpMethod;
    private HttpHeaders httpHeaders = new HttpHeaders();
    private CommonHttpParamMapNode paramMap;
    private String result;

    private TimeoutCallBack timeoutCallBack = e -> {
        throw e;
    };

    private HttpStatusCallBack httpStatusCallBack = e -> {
        throw e;
    };

    public CommonHttpParamMapNode getRootParamNode() {
        if (paramMap == null)
            paramMap = new CommonHttpParamMapNode(this);
        return paramMap;
    }

    /**
     * 獲取實例
     *
     * @return com.riteny.common.httpclient.CommonHttpRestfulTemplate
     */
    public static CommonHttpRestfulTemplate getInstance() {
        return new CommonHttpRestfulTemplate();
    }

    /**
     * 設置該次調用的授權校驗方法為 Basic Auth 1.0
     *
     * @param username 用戶名
     * @param password 密碼
     * @return com.riteny.common.httpclient.CommonHttpRestfulTemplate
     */
    public CommonHttpRestfulTemplate setBasicAuth(String username, String password) {

        httpHeaders.set("Authorization", "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes()));

        return this;
    }

    /**
     * 設置該次調用的授權校驗方法為 Oauth 2.0
     *
     * @param accessToken 授權令牌
     * @return com.riteny.common.httpclient.CommonHttpRestfulTemplate
     */
    public CommonHttpRestfulTemplate setOAuth2(String accessToken) {

        httpHeaders.set("Authorization", "Bearer " + accessToken);

        return this;
    }

    /**
     * 設置連接超時時間
     *
     * @param connectionTimeout 超時時間
     * @return com.riteny.common.httpclient.CommonHttpRestfulTemplate
     */
    public CommonHttpRestfulTemplate setConnectionTimeout(Integer connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
        return this;
    }

    /**
     * 設置讀取超時時間
     *
     * @param readTimeout 超時時間
     * @return com.riteny.common.httpclient.CommonHttpRestfulTemplate
     */
    public CommonHttpRestfulTemplate setReadTimeout(Integer readTimeout) {
        this.readTimeout = readTimeout;
        return this;
    }


    /**
     * 設置調用的路徑
     *
     * @param url 路徑
     * @return com.riteny.common.httpclient.CommonHttpRestfulTemplate
     */
    public CommonHttpRestfulTemplate setUrl(String url) {
        this.url = url;
        return this;
    }

    /**
     * 設置調用方法
     *
     * @param httpMethod 調用方法 參考本類的靜態變量
     * @return com.riteny.common.httpclient.CommonHttpRestfulTemplate
     */
    public CommonHttpRestfulTemplate setHttpMethod(Integer httpMethod) {
        this.httpMethod = httpMethod;
        return this;
    }

    /**
     * 添加請求頭
     *
     * @param key   key
     * @param value value
     * @return com.riteny.common.httpclient.CommonHttpRestfulTemplate
     */
    public CommonHttpRestfulTemplate addHeader(String key, String value) {

        httpHeaders.set(key, value);

        return this;
    }

    /**
     * 添加請求頭（批量）
     *
     * @param headers 請求頭
     * @return com.riteny.common.httpclient.CommonHttpRestfulTemplate
     */
    public CommonHttpRestfulTemplate addHeaders(JSONObject headers) {

        headers.forEach((key, value) -> {
            httpHeaders.add(String.valueOf(key), String.valueOf(value));
        });

        return this;
    }

    /**
     * 設置儅出現連接超時異常時，需要完成的操作
     * 默認抛出異常
     *
     * @param e 異常
     * @return com.riteny.common.httpclient.CommonHttpRestfulTemplate
     */
    public CommonHttpRestfulTemplate whenTimeoutHandler(TimeoutCallBack e) {

        this.timeoutCallBack = e;

        return this;
    }

    /**
     * 設置當出現HTTP 異常時，需要完成的操作
     *
     * @param e 異常
     * @return com.riteny.common.httpclient.CommonHttpRestfulTemplate
     */
    public CommonHttpRestfulTemplate whenHttpStatusCallBack(HttpStatusCallBack e) {

        this.httpStatusCallBack = e;

        return this;
    }

    /**
     * 獲取JSON 對象格式的 返回值
     *
     * @return com.riteny.common.httpclient.CommonHttpRestfulTemplate
     */
    public JSONObject getResultJsonObj() {
        return JSONObject.fromObject(result);
    }

    /**
     * 獲取JSON Array 格式的返回值
     *
     * @return com.riteny.common.httpclient.CommonHttpRestfulTemplate
     */
    public JSONArray getResultJsonArray() {
        return JSONArray.fromObject(result);
    }

    /**
     * 獲取字符串格式的返回值
     *
     * @return com.riteny.common.httpclient.CommonHttpRestfulTemplate
     */
    public String getResult() {
        return result;
    }

    /**
     * 執行調用
     *
     * @return com.riteny.common.httpclient.CommonHttpRestfulTemplate
     */
    public CommonHttpRestfulTemplate execute() {

        //step1 設置超時時間
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(connectionTimeout);// 设置超时
        requestFactory.setReadTimeout(readTimeout);
        restTemplate = new RestTemplate(requestFactory);

        //step2 設置請求頭格式
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        httpHeaders.add("Accept", MediaType.APPLICATION_JSON.toString());

        try {
            switch (httpMethod) {
                case 1: {
                    executeGetMethod();
                    break;
                }
                case 2: {
                    executePostMethod();
                    break;
                }
                case 3: {
                    executePutMethod();
                    break;
                }
                case 4: {
                    executeDeleteMethod();
                    break;
                }
                default: {
                    throw new CommonHttpClientException(CommonHttpClientException.STATUS_ERROR, "Not support http method .");
                }
            }
        } catch (ResourceAccessException e) {
            if (e.getRootCause() instanceof SocketTimeoutException)
                timeoutCallBack.doInCallBack(e);
            throw e;
        } catch (HttpClientErrorException e) {
            httpStatusCallBack.doInCallBack(e);
        }

        return this;
    }

    /**
     * 執行 GET 請求
     */
    private void executeGetMethod() {

        long startTime = System.currentTimeMillis();
        System.out.println("#Request : " + url + " #Method : GET #Param : " + paramMap);

        //step1 檢查是否有參數，並進行參數拼接
        if (paramMap.size() > 0) {

            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("?");

            paramMap.forEach((key, value) -> {
                stringBuilder.append(key).append("=").append(value).append("&");
            });

            url += stringBuilder.substring(0, stringBuilder.length() - 1);
        }

        try {
            //step2 發起調用
            HttpEntity<Map> httpEntity = new HttpEntity<>(null, httpHeaders);

            HttpEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);

            long useTime = System.currentTimeMillis() - startTime;
            System.out.println("#Response : " + url + " #Method : GET  #use time(ms) : " + useTime + " #Result : " + result);

            result = response.getBody();
        } catch (Exception e) {
            long useTime = System.currentTimeMillis() - startTime;
            System.out.println("#Response : " + url + " #Method : GET  #use time(ms) : " + useTime + " #Result : " + e.getMessage());
            throw e;
        }
    }

    /**
     * 執行 POST 請求
     */
    private void executePostMethod() {

        long startTime = System.currentTimeMillis();
        System.out.println("#Request : " + url + " #Method : POST #Param : " + JSONObject.fromObject(paramMap));

        try {
            HttpEntity<Map> httpEntity = new HttpEntity<>(paramMap, httpHeaders);

            HttpEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);

            long useTime = System.currentTimeMillis() - startTime;
            System.out.println("#Response : " + url + " #Method : POST  #use time(ms) : " + useTime + " #Result : " + result);

            result = response.getBody();
        } catch (Exception e) {
            long useTime = System.currentTimeMillis() - startTime;
            System.out.println("#Response : " + url + " #Method : POST  #use time(ms) : " + useTime + " #Result : " + e.getMessage());
            throw e;
        }
    }

    /**
     * 執行 PUT 請求
     */
    private void executePutMethod() {

        long startTime = System.currentTimeMillis();
        System.out.println("#Request : " + url + " #Method : PUT #Param : " + paramMap);

        try {
            HttpEntity<Map> httpEntity = new HttpEntity<>(paramMap, httpHeaders);

            HttpEntity<String> response = restTemplate.exchange(url, HttpMethod.PUT, httpEntity, String.class);

            long useTime = System.currentTimeMillis() - startTime;
            System.out.println("#Response : " + url + " #Method : PUT  #use time(ms) : " + useTime + " #Result : " + result);

            result = response.getBody();
        } catch (Exception e) {
            long useTime = System.currentTimeMillis() - startTime;
            System.out.println("#Response : " + url + " #Method : GET  #use time(ms) : " + useTime + " #Result : " + e.getMessage());
            throw e;
        }
    }

    /**
     * 執行 DELETE 請求
     */
    private void executeDeleteMethod() {

        long startTime = System.currentTimeMillis();
        System.out.println("#Request : " + url + " #Method : DELETE #Param : " + paramMap);

        try {
            HttpEntity<Map> httpEntity = new HttpEntity<>(paramMap, httpHeaders);

            HttpEntity<String> response = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, String.class);
            long useTime = System.currentTimeMillis() - startTime;
            System.out.println("#Response : " + url + " #Method : DELETE  #use time(ms) : " + useTime + " #Result : " + result);

            result = response.getBody();
        } catch (Exception e) {
            long useTime = System.currentTimeMillis() - startTime;
            System.out.println("#Response : " + url + " #Method : GET  #use time(ms) : " + useTime + " #Result : " + e.getMessage());
            throw e;
        }
    }


    public String getUrl() {
        return url;
    }

    public Integer getHttpMethod() {
        return httpMethod;
    }

    public Integer getConnectionTimeout() {
        return connectionTimeout;
    }

    public Integer getReadTimeout() {
        return readTimeout;
    }

    public interface TimeoutCallBack {
        void doInCallBack(ResourceAccessException e) throws ResourceAccessException;
    }

    public interface HttpStatusCallBack {
        void doInCallBack(HttpClientErrorException e) throws HttpClientErrorException;
    }
}
